﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufBase.h"
#include "GnufQtObjectPtr.h"
#include "GnufIQtMaskHandler.h"

namespace gnuf {

    enum class ELayoutPattern : std::uint8_t
    {
        eUnknown,
        eRow,
        eColumn,
        eFill
    };

    /*
      Origin (0, 0)
        +--------------------------------------------------------------------> X
        |
        |                                       ______________[ ArrangementArea ]
        |                                      /
        |    Point (x, y)                     /
        |   +---o- - - - - - - - - - - - - - × - - - - -o
        |   |   .                  ^                    .
        |   |   .                 Top                   .
        |   |   .                  v                ×   .
        |   |   .       +-----------------------+    \__._____[ Padding ]
        |   |   .       |                       |       .
        |   |   .       |                       |       .
        |   H   .   L   |                       |   R   .
        |   e   .   e   |                       |   i   .
        |   i   .<  f  >|                       |<  g  >.
        |   g   .   t   |                       |   h   .
        |   h   .       |                       |   t   .
        |   t   .       |                 ×     |       .
        |   |   .       |                  \    |       .
        |   |   .       +-------------------\---+       .
        |   |   .                  ^         \          .
        |   |   .                Bottom       \_________._____[ ArrangementAreaContent ]
        |   |   .                  v                    .
        |   +---o- - - - - - - - - - - - - - - - - - - -o
        |       |                                       |
        |       +--------------- Width -----------------+
        |
        |
        v
        Y
    */

    class ArrangementAreaContent;
    class DesktopArrangementArea;
    class IsolatedArrangementArea;
    class TileArrangementArea;
    class DockingArrangementArea;
    class ArrangementAreaEvent;
    class ArrangementArea;
    class DisplayUnit;
    class QtMask;
    class Color;
    class ScaleFactor;

    class GNUF_BASE_EXPORT IArrangementAreaObserver : public gcmp::WeakReferenceable
    {
    public:
        explicit IArrangementAreaObserver() {}
        virtual ~IArrangementAreaObserver();
        bool isObserving() const { return (nullptr != observingArrangementArea()); }

        virtual void onArrangementAreaEvent(ArrangementAreaEvent *) {}
        virtual void onLayoutRefreshed(const ArrangementArea * const) {}
        virtual void onClickedOnMaskOf(const ArrangementArea * const) {}
    protected:
        const ArrangementArea * observingArrangementArea() const;
        ArrangementArea * observingArrangementArea();
    private:
        friend class ArrangementArea;
        gcmp::WeakPtr< ArrangementArea > m_wpObservingArrangementArea;
    };
    
    class IArrangementAreaAid : public gcmp::WeakReferenceable
    {
        DEFINE_CAST_BASE(IArrangementAreaAid)
    public:
        virtual bool onComputeLayout(ArrangementArea *) { return false; }
    };

    class GNUF_BASE_EXPORT ArrangementArea
        : public IQtMaskHandler
    {
        CANNOT_COPY_OR_ASSIGN(ArrangementArea)
    public:
        enum class EType : std::uint8_t
        {
            eDesktop,
            eOrdinary,
            eDocking,
            eFloating,
            eIsolated,
            eTile
        };
    protected:
        ArrangementArea(ArrangementArea::EType, ArrangementArea *, ELayoutPattern);
    public:
        virtual ~ArrangementArea();

    public:
        inline ArrangementArea::EType type() const { return m_type; }
        inline bool isRoot() const { return (nullptr == m_parent); }
        inline bool hasParent() const { return (nullptr != parent()); }
        inline bool hasGrandparent() const { return (nullptr != grandparent()); }
        int index() const;
        ArrangementArea * childAt(const int);
        inline ArrangementArea * parent() const { return m_parent; }
        inline ArrangementArea * grandparent() const {
            return (nullptr != m_parent) ? m_parent->parent() : nullptr;
        }
        const DesktopArrangementArea * desktop() const;
              DesktopArrangementArea * desktop();
        const IsolatedArrangementArea * isolatedArrangementArea() const;
              IsolatedArrangementArea * isolatedArrangementArea();
        const TileArrangementArea * tileArrangementArea() const;
              TileArrangementArea * tileArrangementArea();
        bool isFloating() const;
        inline ELayoutPattern layoutPattern() const { return m_layoutPattern; }
        void setLayoutPattern(ELayoutPattern val) { m_layoutPattern = val; }
        inline void setKey(const std::wstring & key) { m_key = key; }
        void setInternalKey(const std::string & internalKey) { m_internalKey = internalKey; }
        const std::string internalKey() const { return m_internalKey; }
        inline const std::wstring & key() const { return m_key; }
        const ArrangementArea * search(
            std::function<bool(const ArrangementArea *)>, 
            bool fromUpToDown) const;
        ArrangementArea * search(
            std::function<bool(const ArrangementArea *)>,
            bool fromUpToDown);

        struct Point { int x, y; };
        struct Size { int width, height; };
        struct Rect { ArrangementArea::Point pos; ArrangementArea::Size size; };
        inline int x() const { return m_geometry.pos.x; }
        inline int y() const { return m_geometry.pos.y; }
        inline int width() const { return m_geometry.size.width; }
        inline int height() const { return m_geometry.size.height; }
        inline const ArrangementArea::Point & pos() const { return m_geometry.pos; }
        ArrangementArea::Point mapToGlobal(ArrangementArea::Point) const;
        ArrangementArea::Point mapToParent(ArrangementArea::Point) const;
        ArrangementArea::Point mapToDesktop(ArrangementArea::Point) const;
        inline const ArrangementArea::Size & size() const { return m_geometry.size; }
        inline const ArrangementArea::Rect & geometry() const { return m_geometry; }

        void setX(const int);
        void setY(const int);
        void setWidth(const int);
        void setHeight(const int);

        void setFixedWidth(int);
        void setFixedHeight(int);
        struct Stretchable { bool horizontal, vertical; };
        const ArrangementArea::Stretchable & stretchable() const {
            return m_stretchable;
        }
        
        inline void setExpectedWidth(int w) { m_expectedSize.width = w; }
        inline void setExpectedHeight(int h) { m_expectedSize.height = h; }
        inline int expectedWidth() const { return m_expectedSize.width; }
        inline int expectedHeight() const { return m_expectedSize.height; }
        inline const ArrangementArea::Size & expectedSize() const {
                return m_expectedSize;
        }

        // By default, the minimize width is 20, and the minimum height is 20.
        inline void setMinimumWidth(int w);
        inline void setMinimumHeight(int h);
        inline int minimumWidth() const { return m_minimumSize.width; }
        inline int minimumHeight() const { return m_minimumSize.height; }
        inline const ArrangementArea::Size & minimizeSize() const {
            return m_minimumSize;
        }

        inline void setMaximumWidth(int w);
        inline void setMaximumHeight(int h);
        inline int maximumWidth() const { return m_maximumSize.width; }
        inline int maximumHeight() const { return m_maximumSize.height; }
        inline const ArrangementArea::Size& maximumSize() const {
            return m_maximumSize;
        }

        bool contains(const int globalX, const int globalY, const int tolerance) const;

        struct Padding { int left, top, right, bottom; };
        inline void setPadding(const ArrangementArea::Padding & val) { m_padding = val; }
        inline void setPadding(int left, int top, int right, int bottom) {
            m_padding.left = left;
            m_padding.top = top;
            m_padding.right = right;
            m_padding.bottom = bottom;
        }
        const ArrangementArea::Padding & padding() const { return m_padding; }
        
        enum class EDirection : std::uint8_t {
            eLeft   = 0,
            eTop    = 1,
            eRight  = 2,
            eBottom = 3
        };
        inline void setDockingAccepted(EDirection direction, bool accepted) {
            m_dockingAccepted[static_cast<size_t>(direction)] = accepted;
        }
        inline bool doesAcceptDockingAt(EDirection direction) const {
            return m_dockingAccepted[static_cast<size_t>(direction)];
        }

        // Add an arrangement area.
        ArrangementArea * addArrangementArea(ELayoutPattern);
        // Add a tile arrangement area.
        TileArrangementArea * addTileArrangementArea(ELayoutPattern, bool asLessee);
        // Insert an new docking arrangement area.
        DockingArrangementArea * addDockingArrangementAreaAt(int index, 
            int expectedWidth, int expectedHeight);
        // Remove an existing arrangement area. 
        // The ownership intends to take the ownership, from me.
        void removeArrangementArea(const ArrangementArea *, 
            gcmp::OwnerPtr< ArrangementArea > * ownership = nullptr);
        // Insert an existing arrangement area at the index.
        void insertArrangementArea(gcmp::OwnerPtr< ArrangementArea > &, int index);
        // Move an existing arrangement area from the current parent to the target.
        static void moveArrangementArea(const ArrangementArea *,
            ArrangementArea * target, const int index = -1);
        
        // Set/Get the arrangement area content.
        void setArrangementAreaContent(ArrangementAreaContent *);
        void transferArrangementAreaContentTo(ArrangementArea *);
        void disconnectFromArrangementAreaContent();
        inline ArrangementAreaContent * arrangementAreaContent() const {
            return m_arrangementAreaContent;
        }

        // Return true if the content does not exist. Otherwise, return false.
        bool isVirtual() const;
        // Return true if the content exists. Otherwise, return false.
        bool isPractical() const;
        
        typedef std::list< gcmp::OwnerPtr< ArrangementArea > > Children;
        typedef std::vector< ArrangementArea * > RawChildren;
        inline const ArrangementArea::Children & children() const { 
            return m_children;
        }
        ArrangementArea::RawChildren rawChildren();

        inline void setExpectedVisibility(bool vis) { m_expectedVisibility = vis; }
        inline bool expectedVisibility() const {
            return (0 == m_suppressedExpectedVisibility /* unsupressed */ ? m_expectedVisibility :
                        (m_suppressedExpectedVisibility > 0));  // -1:invisible, 1:visible
        }
        inline void suppressExpectedVisiblityWith(bool vis) {
            m_suppressedExpectedVisibility = (vis ? 1 : -1);    // -1:invisible, 1:visible
        }
        inline void unsuppressExpectedVisiblity() {
            m_suppressedExpectedVisibility = 0;
        }
        inline bool isExpectedVisibilitySuppressed() const {
            return (0 != m_suppressedExpectedVisibility);       // 0:unsupressed
        }
        inline void setVisible(bool vis) { m_visible = vis; }
        inline bool visible() const { return m_visible; }
        bool hasVisibleChild(bool checkExpectedVisibility = false) const;

        void setOpacity(float, bool recursively = true);
        inline float opacity() const { return m_opacity; }

        // Submit the changes. For example, after setting the expected visibility
        // by setExpectedVisibility(false/true), need to call this method to submit.
        void submit(bool toGrandparent = false);

        void sendEvent(ArrangementAreaEvent *);

        void setWantToAddDynamicPanelsBehind(bool val) {
            m_wantToAddDynamicPanelsBehind = val;
        }
        bool wantToAddDynamicPanelsBehind() const {
            return m_wantToAddDynamicPanelsBehind;
        }

        void setAsStretch(bool val) { m_asStretch = val; }
        bool asStretch() const { return m_asStretch; }

        void setAsDisplayUnit(bool);
        bool asDisplayUnit() const { return (nullptr != m_displayUnit.get()); }
        const DisplayUnit * displayUnit() const;

        void setDirectDisplayUnitActive(const bool);
        bool isDirectDisplayUnitActive() const;

        int positionForDynamicPanels() const;

        // Add/Remove an observer that intends to get notified when the layout
        // is refreshed, the mask is clicked, the observed arrangement area is
        // on event, and etc.
        void    addObserver(IArrangementAreaObserver *);
        void removeObserver(const IArrangementAreaObserver *);

        ///\brief Mask the Arrangement Area.
        ///\param masked: want to mask if true.
        ///\param pColor: give the color to the mask. If nullptr, use the default color.
        ///\param overall: masked overall. Once masked overall, not only this Arrangement
        /// Area is masked, but also the Window Title Bar and other sub-windows (implemented
        /// by Qt or in C#) both are masked.
        void setMasked(bool masked, const gnuf::Color * pColor = nullptr, bool overall = false);
        ///\return Return true if masked now.
        ///\param pOverall: return if masked overall.
        bool isMasked(bool * pOverall = nullptr) const;
        ///\return Return true if as masked. An Arrangement Area mightn't
        /// be masked, however it expects to be as masked, in some cases.
        virtual bool isAsMasked(bool * pOverall = nullptr) const { return isMasked(pOverall); }

        // Return the scale factor.
        virtual const ScaleFactor & scaleFactor() const;
        
        void setAid(IArrangementAreaAid *);
        const IArrangementAreaAid * aid() const {
            return (m_wpAid.IsValid() ? m_wpAid.Get() : nullptr);
        }
        IArrangementAreaAid * aid() {
            return (m_wpAid.IsValid() ? m_wpAid.Get() : nullptr);
        }

        void setWidthAdjusted (bool);
        void setHeightAdjusted(bool);
        bool isSizeAdjusted  () const { return (0 != m_sizeAdjusted); }
        bool isWidthAdjusted () const { return (0 != (0xF0 & m_sizeAdjusted)); }
        bool isHeightAdjusted() const { return (0 != (0x0F & m_sizeAdjusted)); }

    protected:
        void erase();

        virtual void onPreLayoutRefreshed() {}
        virtual void onPostLayoutRefreshed() {}

        virtual void onEvent(ArrangementAreaEvent *);

        // Inherits from IQtMaskHandler...
        virtual void onClickedOn(const QtMask * const) override;
        virtual bool onIsMasked(const QtMask * const, const std::uint64_t, bool) override;

    private:
        DisplayUnit * displayUnit();
        // Refresh the layout and the children' layouts, recursively.
        void refreshLayout();
        void insertArrangementAreaAt(gcmp::OwnerPtr< ArrangementArea > &, int);
        void clearAdjoinments();
        ArrangementArea::Point mapUpNotUntil(
            ArrangementArea::Point,
            const ArrangementArea * const) const;
        
        typedef std::list< gcmp::WeakPtr< IArrangementAreaObserver > > Observers;

        template <class TFunction>
        void notifyObservers(const TFunction & fun)
        {
            // While notifying, it is possible to call removeObserver() method,
            // thus make this notification for the temporary ones, to prevent
            // from the crash that the changed container causes.
            ArrangementArea::Observers ones(m_observers);
            for (auto & one : ones) {
                if (one.IsValid())
                    fun(one.Get());
            }
        }

        const ArrangementArea::EType m_type;
        ArrangementArea * m_parent;
        ELayoutPattern m_layoutPattern;
        std::wstring m_key;
        std::string m_internalKey;
        ArrangementArea::Rect m_geometry;
        ArrangementArea::Size m_expectedSize, m_minimumSize, m_maximumSize;
        ArrangementArea::Padding m_padding;
        ArrangementArea::Stretchable m_stretchable;
        ArrangementAreaContent * m_arrangementAreaContent;
        ArrangementArea::Children m_children;
        bool m_expectedVisibility, m_visible;
        std::int8_t m_suppressedExpectedVisibility = 0; // -1:invisible, 0:unsupressed, 1:visible
        std::vector<bool> m_dockingAccepted;
        float m_opacity;
        bool m_wantToAddDynamicPanelsBehind;
        bool m_asStretch;
        gcmp::OwnerPtr<DisplayUnit> m_displayUnit;
        ArrangementArea::Observers m_observers;
        gcmp::QtAutoPtr< QtMask > m_apQtMask;
        gcmp::WeakPtr< IArrangementAreaAid > m_wpAid;
        std::uint8_t m_sizeAdjusted = 0;
    };
}
